import os
import importlib
import traceback
import json

from datetime import datetime
from typing import List, Dict
from threading import Thread
from pathlib import Path
from inspect import getfile

from vnsumscope.event import Event, EventEngine
from vnsumscope.trader.engine import BaseEngine, MainEngine
from vnsumscope.trader.constant import Interval
from vnsumscope.trader.utility import extract_vt_symbol
from vnsumscope.trader.object import HistoryRequest
from vnsumscope.trader.rqdata import rqdata_client
from vnsumscope.trader.database import database_manager
from vnsumscope.database.sqlite import records_manager
from vnsumscope.app.portfolio_strategy import StrategyTemplate
from vnsumscope.app.portfolio_strategy.backtesting import (
    BacktestingEngine, OptimizationSetting
)
from vnsumscope.app.portfolio_strategy.base import BacktestingMode

APP_NAME = "PortfolioBacktester"

EVENT_BACKTESTER_LOG = "eBacktesterLog"
EVENT_BACKTESTER_BACKTESTING_FINISHED = "eBacktesterBacktestingFinished"
EVENT_BACKTESTER_BACKTESTING_ERROR = "eBacktesterBacktestingError"
EVENT_BACKTESTER_OPTIMIZATION_FINISHED = "eBacktesterOptimizationFinished"
EVENT_BACKTESTER_OPTIMIZATION_ERROR = "eBacktesterOptimizationError"


class PortfolioBacktesterEngine(BaseEngine):
    """
    For running portfolio strategy backtesting.
    """

    def __init__(self, main_engine: MainEngine, event_engine: EventEngine):
        """"""
        super().__init__(main_engine, event_engine, APP_NAME)

        self.classes = {}
        self.backtesting_engine = None
        self.thread = None

        # Backtesting reuslt
        self.result_df = None
        self.result_statistics = None
        self.record_trade = None
        self.record_order = None

        # Optimization result
        self.result_values = None

    def init_engine(self, strategy_path):
        """"""
        self.write_log("初始化组合回测引擎")

        self.backtesting_engine = BacktestingEngine()
        # Redirect log from backtesting engine outside.
        self.backtesting_engine.output = self.write_log

        self._strategy_path = strategy_path
        self.load_strategy_class()
        self.write_log("策略文件加载完成")

        self.init_rqdata()

    def init_rqdata(self):
        """
        Init RQData client.
        """
        result = rqdata_client.init()
        if result:
            self.write_log("RQData数据接口初始化成功")

    def write_log(self, msg: str):
        """"""
        event = Event(EVENT_BACKTESTER_LOG)
        event.data = msg
        self.event_engine.put(event)

    def load_strategy_class(self):
        """
        Load strategy class from source code.
        """
        strategy_file_name = Path(self._strategy_path).stem
        if 'app' in self._strategy_path:
            module_name = f'vnsumscope.app.portfolio_strategy.strategies.{strategy_file_name}'
        else:
            from vnsumscope.trader.utility import CUSTOM_PORTFOLIO_MODULE
            module_name = f'{CUSTOM_PORTFOLIO_MODULE}.{strategy_file_name}'
        try:
            module = importlib.import_module(module_name)
            importlib.reload(module)
            for name in dir(module):
                value = getattr(module, name)
                if (isinstance(value, type) and issubclass(value, StrategyTemplate) and value is not StrategyTemplate):
                    self.classes[value.__name__] = value
        except:  # noqa
            msg = f"策略文件{self._strategy_path}加载失败，触发异常：\n{traceback.format_exc()}"
            self.write_log(msg)

    def reload_strategy_class(self):
        """"""
        self.classes.clear()
        self.load_strategy_class()
        self.write_log("策略文件重载刷新完成")

    def get_strategy_class_names(self):
        """"""
        return list(self.classes.keys())

    def run_backtesting(
            self,
            class_name: str,
            vt_symbols: List[str],
            interval: str,
            start: datetime,
            end: datetime,
            rates: Dict[str, float],
            slippages: Dict[str, float],
            sizes: Dict[str, float],
            priceticks: Dict[str, float],
            capital: int,
            setting: dict
    ):
        """"""
        self.result_df = None
        self.result_statistics = None

        engine = self.backtesting_engine
        engine.clear_data()

        if interval == Interval.TICK.value:
            mode = BacktestingMode.TICK
        else:
            mode = BacktestingMode.BAR

        engine.set_parameters(
            vt_symbols=vt_symbols,
            interval=interval,
            start=start,
            end=end,
            rates=rates,
            slippages=slippages,
            sizes=sizes,
            priceticks=priceticks,
            capital=capital,
        )

        strategy_class = self.classes[class_name]

        try:
            engine.add_strategy(
                strategy_class,
                setting
            )
            parameters = {
                'vt_symbol': vt_symbols,
                'interval': interval,
                'start': start,
                'end': end,
                'rate': rates,
                'slippage': slippages,
                'size': sizes,
                'pricetick': priceticks,
                'capital': capital,
                'mode': mode,
                'params': json.dumps(setting)
            }
            record = records_manager.create_record(
                strategy_path=self._strategy_path,
                params=parameters
            )
            engine.load_data()
            engine.run_backtesting()
            self.result_df = engine.calculate_result()
            self.result_statistics = engine.calculate_statistics(output=False)
            self.record_trade = list(engine.trades.values())
            self.record_order = list(engine.limit_orders.values())
        except Exception:
            msg = f"策略回测失败，触发异常：\n{traceback.format_exc()}"
            self.write_log(msg)
            event = Event(EVENT_BACKTESTER_BACKTESTING_ERROR)
            data = {'status': 'failed', 'total_return': None}
        else:
            msg = "策略回测完成"
            self.write_log(msg)
            event = Event(EVENT_BACKTESTER_BACKTESTING_FINISHED)
            data = {'status': 'success', 'total_return': self.result_statistics['total_return']}
            records_manager.save_trade(record.id, self.record_trade)
            records_manager.save_order(record.id, self.record_order)
            records_manager.save_statistic(record.id,self.result_statistics)
            records_manager.save_daily_result(record.id,self.result_df,'portfolio')
        finally:
            records_manager.update_record(record_id=record.id, data=data)
            self.event_engine.put(event)
            self.thread = None

    def start_backtesting(
            self,
            class_name: str,
            vt_symbols: List[str],
            interval: str,
            start: datetime,
            end: datetime,
            rates: Dict[str, float],
            slippages: Dict[str, float],
            sizes: Dict[str, float],
            priceticks: Dict[str, float],
            capital: int,
            setting: dict
    ):
        if self.thread:
            self.write_log("已有任务在运行中，请等待完成")
            return False

        self.write_log("-" * 40)
        self.thread = Thread(
            target=self.run_backtesting,
            args=(
                class_name,
                vt_symbols,
                interval,
                start,
                end,
                rates,
                slippages,
                sizes,
                priceticks,
                capital,
                setting
            )
        )
        self.thread.start()

        return True

    def get_result_df(self):
        """"""
        return self.result_df

    def get_result_statistics(self):
        """"""
        return self.result_statistics

    def get_result_values(self):
        """"""
        return self.result_values

    def get_default_setting(self, class_name: str):
        """"""
        strategy_class = self.classes[class_name]
        return strategy_class.get_class_parameters()

    def run_optimization(
            self,
            class_name: str,
            vt_symbols: List[str],
            interval: str,
            start: datetime,
            end: datetime,
            rates: Dict[str, float],
            slippages: Dict[str, float],
            sizes: Dict[str, float],
            priceticks: Dict[str, float],
            capital: int,
            optimization_setting: OptimizationSetting,
            use_ga: bool
    ):
        """"""
        if use_ga:
            self.write_log("开始遗传算法参数优化")
        else:
            self.write_log("开始多进程参数优化")

        self.result_values = None

        engine = self.backtesting_engine
        engine.clear_data()

        if interval == Interval.TICK:
            mode = BacktestingMode.TICK
        else:
            mode = BacktestingMode.BAR

        engine.set_parameters(
            vt_symbols=vt_symbols,
            interval=interval,
            start=start,
            end=end,
            rates=rates,
            slippages=slippages,
            sizes=sizes,
            priceticks=priceticks,
            capital=capital,
        )
        try:
            strategy_class = self.classes[class_name]
            engine.add_strategy(
                strategy_class,
                {}
            )

            if use_ga:
                self.result_values = engine.run_ga_optimization(
                    optimization_setting,
                    output=False
                )
            else:
                self.result_values = engine.run_optimization(
                    optimization_setting,
                    output=False
                )
        except Exception:
            self.write_log("多进程参数优化失败")
            msg = f"策略回测失败，触发异常：\n{traceback.format_exc()}"
            self.write_log(msg)
            event = Event(EVENT_BACKTESTER_OPTIMIZATION_ERROR)
        else:
            self.write_log("多进程参数优化完成")
            event = Event(EVENT_BACKTESTER_OPTIMIZATION_FINISHED)
        finally:
            self.event_engine.put(event)
            self.thread = None

    def start_optimization(
            self,
            class_name: str,
            vt_symbols: List[str],
            interval: str,
            start: datetime,
            end: datetime,
            rates: Dict[str, float],
            slippages: Dict[str, float],
            sizes: Dict[str, float],
            priceticks: Dict[str, float],
            capital: int,
            optimization_setting: OptimizationSetting,
            use_ga: bool
    ):
        if self.thread:
            self.write_log("已有任务在运行中，请等待完成")
            return False

        self.write_log("-" * 40)
        self.thread = Thread(
            target=self.run_optimization,
            args=(
                class_name,
                vt_symbols,
                interval,
                start,
                end,
                rates,
                slippages,
                sizes,
                priceticks,
                capital,
                optimization_setting,
                use_ga
            )
        )
        self.thread.start()

        return True

    def run_downloading(
            self,
            vt_symbol: str,
            interval: str,
            start: datetime,
            end: datetime
    ):
        """
        Query bar data from RQData.
        """
        self.write_log(f"{vt_symbol}-{interval}开始下载历史数据")

        try:
            symbol, exchange = extract_vt_symbol(vt_symbol)
        except ValueError:
            self.write_log(f"{vt_symbol}解析失败，请检查交易所后缀")
            self.thread = None
            return

        req = HistoryRequest(
            symbol=symbol,
            exchange=exchange,
            interval=Interval(interval),
            start=start,
            end=end
        )

        contract = self.main_engine.get_contract(vt_symbol)

        try:
            # If history data provided in gateway, then query
            if contract and contract.history_data:
                data = self.main_engine.query_history(
                    req, contract.gateway_name
                )
            # Otherwise use RQData to query data
            else:
                data = rqdata_client.query_history(req)

            if data:
                database_manager.save_bar_data(data)
                self.write_log(f"{vt_symbol}-{interval}历史数据下载完成")
            else:
                self.write_log(f"数据下载失败，无法获取{vt_symbol}的历史数据")
        except Exception:
            msg = f"数据下载失败，触发异常：\n{traceback.format_exc()}"
            self.write_log(msg)

        # Clear thread object handler.
        self.thread = None

    def start_downloading(
            self,
            vt_symbol: str,
            interval: str,
            start: datetime,
            end: datetime
    ):
        if self.thread:
            self.write_log("已有任务在运行中，请等待完成")
            return False

        self.write_log("-" * 40)
        self.thread = Thread(
            target=self.run_downloading,
            args=(
                vt_symbol,
                interval,
                start,
                end
            )
        )
        self.thread.start()

        return True

    def get_all_trades(self):
        """"""
        return self.backtesting_engine.get_all_trades()

    def get_all_orders(self):
        """"""
        return self.backtesting_engine.get_all_orders()

    def get_all_daily_results(self):
        """"""
        return self.backtesting_engine.get_all_daily_results()

    def get_history_data(self):
        """"""
        return self.backtesting_engine.history_data

    def get_strategy_class_file(self, class_name: str):
        """"""
        strategy_class = self.classes[class_name]
        file_path = getfile(strategy_class)
        return file_path
